home *** CD-ROM | disk | FTP | other *** search
/ Linux Cubed Series 8: LINUX Games / Linux Cubed Series 8 - LINUX Games.iso / games / x11 / rpg / crossfir.92 / crossfir / crossfire-0.92.5 / server / alchemy.c next >
C/C++ Source or Header  |  1996-07-24  |  18KB  |  552 lines

  1.  
  2. /* March 96 - Laid down original code. -b.t. thomas@astro.psu.edu */
  3.  
  4. #include <global.h>
  5. #include <object.h>
  6. #ifndef __CEXTRACT__
  7. #include <sproto.h>
  8. #endif
  9. #include <skills.h>
  10. #include <spells.h>
  11.  
  12. /* define this for some helpful debuging information */
  13. #define ALCHEMY_DEBUG
  14.  
  15. /* define this for loads of (marginal) debuging information */
  16. #define EXTREME_ALCHEMY_DEBUG
  17.  
  18. static char *cauldron_effect [] = { 
  19.  "vibrates briefly",
  20.  "produces a cloud of steam",
  21.  "emits bright flames",
  22.  "pours forth heavy black smoke",
  23.  "emits sparks",
  24.  "shoots out small flames",
  25.  "whines painfully",
  26.  "hiccups loudly",
  27.  "wheezes",
  28.  "burps",
  29.  "shakes",
  30.  "rattles",
  31.  "makes chugging sounds",
  32.  "smokes heavily for a while"
  33. }; 
  34.  
  35.  
  36. /* cauldron_sound() - returns a random selection from cauldron_effect[] */
  37.  
  38. char * cauldron_sound ( void ) {
  39.   int size=sizeof(cauldron_effect)/sizeof(char *);
  40.  
  41.   return cauldron_effect[RANDOM()%size]; 
  42. }
  43.  
  44. /* attempt_do_alchemy() - Main part of the ALCHEMY code. From this we call fctns
  45.  * that take a look at the contents of the 'cauldron' and, using these ingredients, 
  46.  * we construct an integer formula value which is referenced (randomly) against a 
  47.  * formula list (the formula list chosen is based on the # contents of the cauldron).
  48.  *
  49.  * If we get a match between the recipe indicated in cauldron contents and a 
  50.  * randomly chosen one, an item is created and experience awarded. Otherwise 
  51.  * various failure effects are possible (getting worse and worse w/ # cauldron 
  52.  * ingredients). Note that the 'item' to be made can be *anything* listed on 
  53.  * the artifacts list in lib/artifacts which has a recipe listed in lib/formulae.
  54.  *
  55.  * To those wondering why I am using the funky formula index method:
  56.  *   1) I want to match recipe to ingredients regardless of ordering.
  57.  *   2) I want a fast search for the 'right' recipe.
  58.  * 
  59.  * Note: it is just possible that a totally different combination of
  60.  * ingredients will result in a match with a given recipe. This is not a bug!
  61.  * There is no good reason (in my mind) why alchemical processes have to be
  62.  * unique -- such a 'feature' is one reason why players might want to experiment
  63.  * around. :)
  64.  * -b.t.
  65.  */
  66.  
  67. void attempt_do_alchemy(object *caster, object *cauldron) {
  68.   recipelist *fl;
  69.   recipe *rp=NULL;
  70.   int numb,i;
  71.   int formula=0,danger=calc_alch_danger(caster,cauldron);
  72.  
  73.      if(caster->type!=PLAYER) return; /* only players for now */ 
  74.  
  75.       /* if no ingredients, no formula! lets forget it */
  76.      if(!(formula=content_recipe_value(cauldron))) return;
  77.  
  78.      numb=numb_ob_inside(cauldron);
  79.      if((fl=get_formulalist(numb))) {
  80.         int j,ability=SK_level(caster)+cauldron->magic;
  81.  
  82. #ifdef ALCHEMY_DEBUG
  83.     LOG(llevDebug,"Got alchemy ability: %d\n",ability);
  84. #endif
  85.  
  86.         if(QUERY_FLAG(caster,FLAG_WIZ)) { 
  87.         rp=fl->items;
  88.         while(rp && rp->index!=formula) {
  89. #ifdef EXTREME_ALCHEMY_DEBUG
  90.         LOG(llevDebug," grabbed list %d formula: %s of %s (%d)\n",numb,
  91.            rp->arch_name,rp->title,rp->index);
  92. #endif
  93.             rp=rp->next;
  94.         }
  95.         if(rp) {
  96. #ifdef ALCHEMY_DEBUG
  97.         if(strcmp(rp->title,"NONE"))
  98.             LOG(llevDebug,"WIZ got formula: %s of %s\n",
  99.             rp->arch_name,rp->title);
  100.         else 
  101.             LOG(llevDebug,"WIZ got formula: %s\n",rp->arch_name);
  102. #endif
  103.         attempt_recipe(caster,cauldron,rp);
  104.         } else LOG(llevDebug,"WIZ couldnt find formula for ingredients.\n"); 
  105.         return;
  106.     } 
  107.  
  108.     /* player loop--trying to match random recipe */ 
  109.         for(i=ability;i>0;i--) { 
  110.             rp=fl->items;
  111.  
  112.         if(fl->number>1) /* select a random recipe */
  113.                 for(j=RANDOM()%fl->number;j>1;j--) rp=rp->next;
  114.  
  115.             if(rp->index==formula) { /* we matched a recipe */
  116.             if(!(attempt_recipe(caster,cauldron,rp))) 
  117.                break;
  118.             else return;
  119.             }
  120.     }
  121.      } 
  122.  
  123.       /* if we get here, we failed!! */
  124.      alchemy_failure_effect(caster,cauldron,rp,danger);
  125. }
  126.  
  127. /* content_recipe_value()- recipe value of the entire contents of a container. */
  128.  
  129. int content_recipe_value (object *op) {
  130.   char name[MAX_BUF];
  131.   object *tmp=op->inv;
  132.   int tval=0,formula=0;
  133.  
  134.     while(tmp) {
  135.     tval=0;
  136.         strcpy(name,tmp->name);
  137.         if(tmp->title) sprintf(name,"%s %s",tmp->name,tmp->title);
  138.         tval = (strtoint(name) * (tmp->nrof?tmp->nrof:1));
  139. #ifdef ALCHEMY_DEBUG
  140.         LOG(llevDebug,"Got ingredient %d %s(%d)\n",tmp->nrof?tmp->nrof:1,
  141.         name,tval);
  142. #endif   
  143.     formula += tval;
  144.         tmp=tmp->below;
  145.     }   
  146. #ifdef ALCHEMY_DEBUG
  147.     LOG(llevDebug," FORMULA value=%d\n",formula);
  148. #endif
  149.     return formula;
  150. }
  151.  
  152. /* numb_ob_inside() */
  153.  
  154. int numb_ob_inside (object *op) {
  155.   object *tmp=op->inv;
  156.   int number=0,o_number=0;
  157.  
  158.     while(tmp) {
  159.         if(tmp->nrof) number += tmp->nrof;
  160.         else number++;
  161.     o_number++;
  162.         tmp=tmp->below;
  163.     }   
  164. #ifdef ALCHEMY_DEBUG
  165.     LOG(llevDebug,"numb_ob_inside(%s): found %d ingredients\n",op->name,o_number);
  166. #endif 
  167.     return o_number;
  168. }
  169.  
  170. /* attempt_recipe() - essentially a wrapper for make_item_from_recipe() and 
  171.  * insert_ob_in_ob. If the caster has some alchemy skill, then they might
  172.  * gain some exp from (successfull) fabrication of the product. 
  173.  */ 
  174.  
  175. object * attempt_recipe(object *caster, object *cauldron, recipe *rp) { 
  176.   object *item=NULL;   
  177.  
  178.     if((item=make_item_from_recipe(cauldron,rp))!=NULL) { 
  179.        remove_contents(cauldron->inv,item);
  180.        adjust_product(item,caster); /* adj lvl, nrof on caster level */ 
  181.        if(!item->env && (item=insert_ob_in_ob(item,cauldron))==NULL) { 
  182.                 new_draw_info_format(NDI_UNIQUE, 0,caster, 
  183.                    "Your spell causes the %s to explode!",cauldron->name);
  184.                 /* kaboom_cauldron(); */ 
  185.        } else {
  186.         if(find_skill(caster,SK_ALCHEMY)!=NULL) {
  187.                 change_skill(caster,SK_ALCHEMY);
  188.              add_exp(caster,calc_skill_exp(caster,item));
  189.             item->stats.exp=0; /* so when skill id, less xp awarded */
  190.             }
  191.                 new_draw_info_format(NDI_UNIQUE, 0,caster, 
  192.                         "The %s %s.",cauldron->name,cauldron_sound());
  193.        }   
  194.     }    
  195.  
  196.     return item; 
  197.  
  198. /* table of alchemical probability (1-100). Used by adjust_product. */
  199. static int alprob [] = { 10, 25, 35, 45, 50, 55, 65, 68, 70, 73, 75, 76, 77, 78, 79};
  200.  
  201. /* adjust_product() - we adjust the nrof, exp and level of the final product, based
  202.  * on the item's default parameters, and the relevant caster skill level. */
  203.  
  204. void adjust_product(object *item,object *caster) {
  205.    object *skill=NULL;
  206.    int lvl=1,nrof=1,maxnrof;
  207.  
  208.    /* if the caster has alchemy (mental) exp use that level */
  209.    if((skill=find_skill(caster,SK_ALCHEMY)) && skill->exp_obj) 
  210.     lvl = skill->exp_obj->level;
  211.  
  212.    maxnrof = lvl/5;
  213.    if(item->nrof) {
  214.      int prob = maxnrof>(sizeof(alprob)/sizeof(int)) ? 80 : alprob[maxnrof];
  215.      while (maxnrof--)
  216.          if(RANDOM()%100<prob) nrof++;
  217.      item->nrof=nrof;
  218.    }
  219.  
  220.    /* item exp. This will be used later for experience calculation */
  221.    item->stats.exp += lvl*lvl*nrof;
  222.  
  223.    /* item->level = (lvl+item->level)/2; avg between default and caster levels */
  224.    
  225. }
  226.  
  227. /* make_item_from_recipe()- using a list of items and a recipe to make an artifact. */
  228.  
  229. object * make_item_from_recipe(object *cauldron, recipe *rp) {
  230.   artifact *art=NULL;
  231.   object *item=NULL;
  232.  
  233.     if(rp==NULL) return (object *) NULL;
  234.  
  235.     /* Find the appropriate object to transform...*/
  236.     if((item=find_transmution_ob(cauldron->inv,rp))==NULL) {
  237.         LOG(llevDebug,"make_alchemy_item(): failed to create alchemical object.\n");
  238.         return (object *) NULL;
  239.     }
  240.  
  241.     /* Find the appropriate artifact template...*/
  242.     if(strcmp(rp->title,"NONE")) { 
  243.         if((art=locate_recipe_artifact(rp))==NULL) {
  244.             LOG(llevError,"make_alchemy_item(): failed to locate recipe artifact.\n");
  245.             LOG(llevDebug,"  --requested recipe: %s of %s.\n",rp->arch_name,rp->title);
  246.             return (object *) NULL;
  247.     }
  248.         give_artifact_abilities(item,art->item);
  249.     }
  250.  
  251.     if(QUERY_FLAG(cauldron,FLAG_CURSED)) SET_FLAG(item,FLAG_CURSED);
  252.     if(QUERY_FLAG(cauldron,FLAG_DAMNED)) SET_FLAG(item,FLAG_DAMNED);
  253.  
  254.     return item;
  255. }
  256.  
  257.  
  258. /* find_transmution_ob() - looks through the ingredient list, if we find a
  259.  * suitable object in it - we will use that to make the requested artifact.
  260.  * Otherwise the code returns a 'generic' item. -b.t.
  261.  */
  262.  
  263. object * find_transmution_ob ( object *first_ingred, recipe *rp) {
  264.    object *item=NULL;
  265.  
  266.    if(rp->transmute) /* look for matching ingredient/prod archs */ 
  267.         for(item=first_ingred;item;item=item->below)
  268.             if(!strcmp(item->arch->name,rp->arch_name)) break; 
  269.  
  270.     /* failed, create a fresh object. Note no nrof>1 because that would
  271.      * allow players to create massive amounts of artifacts easily */
  272.    if(!item||item->nrof>1)
  273.         item=get_archetype(rp->arch_name);
  274.  
  275. #ifdef ALCHEMY_DEBUG
  276.    LOG(llevDebug,"recipe calls for%stransmution.\n",rp->transmute?" ":" no ");
  277.    if(strcmp(item->arch->name,rp->arch_name))
  278.        LOG(llevDebug,"WARNING: recipe calls for arch: %s\n",rp->arch_name);
  279.    LOG(llevDebug," find_transmutable_ob(): returns arch %s(sp:%d)\n",
  280.         item->arch->name,item->stats.sp);
  281. #endif
  282.  
  283.    return item;
  284. }
  285.  
  286.  
  287. /* alchemy_failure_effect - Ouch. We didnt get the formula we wanted.
  288.  * This fctn simulates the backfire effects--worse effects as the level
  289.  * increases. If SPELL_FAILURE_EFFECTS is defined some really evil things
  290.  * can happen to the would be alchemist. This table probably needs some
  291.  * adjustment for playbalance. -b.t.
  292.  */
  293.  
  294. void alchemy_failure_effect(object *op,object *cauldron,recipe *rp,int danger) {
  295.   int level=0;
  296.  
  297.   if(!op || !cauldron) return; 
  298.  
  299.   if(danger>1) level=RANDOM()%danger+1;
  300.  
  301. #ifdef ALCHEMY_DEBUG
  302.   LOG(llevDebug,"Alchemy_failure_effect(): using level=%d\n",level); 
  303. #endif
  304.  
  305.   /* possible outcomes based on level */
  306.   if(level>10&&level<25) {                   /* INGREDIENTS USED/SLAGGED */
  307.       object *item=NULL;
  308.  
  309.       if(RANDOM()%3) {  /* slag created */
  310.         object *tmp=cauldron->inv;
  311.         int weight=0;
  312.     uint16 material=M_STONE;
  313.  
  314.         while(tmp) { /* slag has coadded ingredient properties */
  315.        weight+=tmp->weight; 
  316.        if(!(material&tmp->material)) 
  317.         material=material|tmp->material; 
  318.        tmp=tmp->below;
  319.         }
  320.         tmp = get_archetype("rock");
  321.         tmp->weight=weight;
  322.         tmp->value=0;
  323.     tmp->material=material;
  324.     free_string(tmp->name);
  325.     tmp->name=add_string("slag");
  326.         item=insert_ob_in_ob(tmp,cauldron);
  327.     CLEAR_FLAG(tmp,FLAG_CAN_ROLL);
  328.     CLEAR_FLAG(tmp,FLAG_NO_PICK);
  329.     CLEAR_FLAG(tmp,FLAG_NO_PASS);
  330.       } 
  331.       remove_contents(cauldron->inv,item);
  332.       new_draw_info_format(NDI_UNIQUE,0,op, 
  333.     "The %s %s.",cauldron->name,cauldron_sound());
  334.       return;
  335.   } else if (level< 40) {                    /* MAKE TAINTED ITEM */
  336.       object *tmp=NULL;
  337.  
  338.       if (!rp) 
  339.        if((rp=get_random_recipe((recipelist *) NULL))==NULL) 
  340.               return;
  341.  
  342.       if((tmp=attempt_recipe(op,cauldron,rp))) { 
  343.            if(!QUERY_FLAG(tmp,FLAG_CURSED)) /* curse it */
  344.            SET_FLAG(tmp,FLAG_CURSED);
  345.  
  346.         /* special stuff for consumables */
  347.            if(tmp->type==POTION||tmp->type==FOOD) {
  348.                 if(tmp->stats.sp&&RANDOM()%2) /* drains magic */
  349.             tmp->stats.sp = SP_REGENERATE_SPELLPOINTS;
  350.                 if(RANDOM()%2) {         /* poisonous */
  351.             tmp->type=FOOD; 
  352.             tmp->stats.hp=RANDOM()%150;
  353.             }
  354.            }
  355.  
  356.                /* change stats downward */
  357.            if(tmp->type!=FOOD) 
  358.           do {
  359.                change_attr_value(&tmp->stats,RANDOM()%6,-1*RANDOM()%3);
  360.              } while (RANDOM()%3);
  361.       }  
  362.       return;
  363.  
  364.   } if(level==40) {                          /* MAKE RANDOM RECIPE */
  365.       recipelist *fl;
  366.       int numb=numb_ob_inside(cauldron);
  367.  
  368.       fl=get_formulalist(numb);
  369.       if(fl &&(rp=get_random_recipe(fl)))
  370.           (void) attempt_recipe(op,cauldron,rp);
  371.       else 
  372.       alchemy_failure_effect(op,cauldron,rp,level-1);
  373.       return;
  374.  
  375.   } else if (level<45) {                    /* INFURIATE NPC's */
  376.        /* this is kind of kludgy I know...*/
  377.       cauldron->enemy=op;
  378.       npc_call_help(cauldron);
  379.       cauldron->enemy=NULL;
  380.  
  381.       alchemy_failure_effect(op,cauldron,rp,level-5);
  382.       return;
  383.  
  384.   }
  385. /* #ifdef SPELL_FAILURE_EFFECTS */ 
  386.    else if (level<50) {                        /* MINOR EXPLOSION/FIREBALL */
  387.       object *tmp;
  388.       remove_contents(cauldron->inv,NULL);
  389.       switch(RANDOM()%3) {
  390.     case 0: 
  391.            tmp=get_archetype("bomb");
  392.          tmp->stats.dam=RANDOM()%level+1;
  393.          tmp->stats.hp=RANDOM()%level+1;
  394.        break;    
  395.              new_draw_info_format(NDI_UNIQUE,0,op,"The %s creates a bomb!",
  396.         cauldron->name);
  397.        break; 
  398.     default:
  399.            tmp=get_archetype("fireball");
  400.          tmp->stats.dam=(RANDOM()%level+1)/5+1;
  401.          tmp->stats.hp=(RANDOM()%level+1)/10+2;
  402.              new_draw_info_format(NDI_UNIQUE,0,op,"The %s erupts in flame!",
  403.         cauldron->name);
  404.        break;
  405.       }
  406.       tmp->x=cauldron->x,tmp->y=cauldron->y;
  407.       insert_ob_in_map(tmp,op->map);
  408.       return;
  409.  
  410.   } else if (level<60) {                    /* CREATE MONSTER */
  411.       new_draw_info_format(NDI_UNIQUE,0,op, 
  412.     "The %s %s.",cauldron->name,cauldron_sound());
  413.       remove_contents(cauldron->inv,NULL);
  414.       return;
  415.  
  416.   } else if (level<80) {                    /* MAJOR FIRE */
  417.       remove_contents(cauldron->inv,NULL);
  418.       fire_arch (cauldron, 0, spellarch[SP_L_FIREBALL], SP_L_FIREBALL, 0);
  419.       new_draw_info_format(NDI_UNIQUE,0,op,"The %s erupts in flame!",
  420.         cauldron->name);
  421.       return;
  422.  
  423.   }
  424. /* #endif SPELL_FAILURE_EFFECTS */
  425.     else if (level<100) {                   /* WHAMMY the CAULDRON */
  426.       if(!QUERY_FLAG(cauldron,FLAG_CURSED))
  427.         SET_FLAG(cauldron,FLAG_CURSED);
  428.       else cauldron->magic--;
  429.       cauldron->magic -= RANDOM()%5;
  430.       if(RANDOM()%2) {
  431.         remove_contents(cauldron->inv,NULL);
  432.         new_draw_info_format(NDI_UNIQUE,0,op,
  433.        "Your %s turns darker then makes a gulping sound!",
  434.            cauldron->name);
  435.       } else
  436.         new_draw_info_format(NDI_UNIQUE,0,op,
  437.         "Your %s becomes darker.",cauldron->name);
  438.       return;
  439.  
  440.   }
  441. /* #ifdef SPELL_FAILURE_EFFECTS */ 
  442.     else if (level<110) {                   /* SUMMON EVIL MONSTERS */
  443.       object *tmp=get_random_mon(level/5);
  444.  
  445.       remove_contents(cauldron->inv,NULL);
  446.       if(!tmp) 
  447.     alchemy_failure_effect(op,cauldron,rp,level);
  448.       else if(summon_hostile_monsters(cauldron,RANDOM()%10+1,tmp->arch->name))
  449.        new_draw_info_format(NDI_UNIQUE, 0,op,
  450.         "The %s %s and then pours forth monsters!",
  451.         cauldron->name,cauldron_sound());
  452.       return;
  453.  
  454.   } else if (level<150) {                   /* COMBO EFFECT */
  455.       int roll = RANDOM()%3+1;
  456.       while(roll) {
  457.     alchemy_failure_effect(op,cauldron,rp,level-39);
  458.     roll--;
  459.       }
  460.       return;
  461.   }
  462.  
  463. /* #endif  SPELL_FAILURE_EFFECTS */
  464.     else if (level==151) {                  /* CREATE RANDOM ARTIFACT */
  465.       object *tmp;
  466.     /* this is meant to be better than prior possiblity, 
  467.      * in this one, we allow *any* valid alchemy artifact
  468.        * to be made (rather than only those on the given
  469.      * formulalist) */
  470.       if(!rp) rp=get_random_recipe((recipelist *) NULL);
  471.       if(rp && (tmp=get_archetype(rp->arch_name))) { 
  472.          generate_artifact(tmp,RANDOM()%((op->level/2)+2)+2);
  473.      if((tmp=insert_ob_in_ob(tmp,cauldron))) { 
  474.         remove_contents(cauldron->inv,tmp);
  475.           new_draw_info_format(NDI_UNIQUE, 0,op,
  476.         "The %s %s.",cauldron->name,cauldron_sound());
  477.      }
  478.       }
  479.       return;
  480.   }
  481. /* #ifdef SPELL_FAILURE_EFFECTS */ 
  482.     else if (level<200) {                   /* MANA STORM - watch out!! */
  483.       new_draw_info(NDI_UNIQUE,0,op,"You unwisely release potent forces!");
  484.       remove_contents (cauldron->inv,NULL);
  485.       cast_mana_storm(op,level);
  486.       return;
  487.   }
  488. /* #endif SPELL_FAILURE_EFFECTS */
  489. }
  490.  
  491.  
  492. /* remove_contents() - all but object "save_item" are elimentated from
  493.  * the container list. Note we have to becareful to remove the inventories
  494.  * of objects in the cauldron inventory (ex icecube has stuff in it).  
  495.  */
  496.  
  497. void remove_contents (object *first_ob, object *save_item) {
  498.   object *next,*tmp=first_ob;
  499.  
  500.     while(tmp) {
  501.         next = tmp->below;
  502.         if(tmp==save_item)
  503.           if(!(tmp=next)) break;
  504.           else next=next->below;
  505.      if(tmp->inv) remove_contents(tmp->inv,NULL);
  506.         remove_ob(tmp);
  507.         free_object(tmp);
  508.         tmp=next;
  509.     }
  510. }
  511.  
  512. /* calc_alch_danger() - "Danger" level will determine how bad the backfire
  513.  * could be if the user fails to concoct a recipe properly. Factors include
  514.  * the number of ingredients, the length of the name of each ingredient,
  515.  * the user's effective level, the user's Int and the enchantment on the
  516.  * mixing device (aka "cauldron"). Higher values of 'danger' indicate more
  517.  * danger. -b.t. 
  518.  */
  519.  
  520. int calc_alch_danger(object *caster,object *cauldron) {
  521.    object *item; 
  522.    char name[MAX_BUF];
  523.    int danger=0,nrofi=0; 
  524.  
  525.     /* Higher level, better cauldrons reduce risk */
  526.    danger -= (SK_level(caster)+(cauldron->magic));
  527.  
  528.     /* Higher Int, lower the risk */
  529.    danger -= 3 * (caster->stats.Int - 15);
  530.  
  531.     /* Ingredients. Longer names usually mean rarer stuff.
  532.      * Thus the backfire is worse. Also, more ingredients
  533.      * means we are attempting a more powerfull potion,
  534.      * and thus the backfire will be worse.  */
  535.    for(item=cauldron->inv;item;item=item->below) {
  536.         strcpy(name,item->name);
  537.         if(item->title) sprintf(name,"%s %s",item->name,item->title);
  538.         danger += (strtoint(name)/1000) + 3;
  539.     nrofi++;
  540.    }
  541.    if(nrofi>1) danger *= nrofi;
  542.  
  543.     /* Using a bad device is *majorly* stupid */
  544.    if(QUERY_FLAG(cauldron,FLAG_CURSED)) danger +=80;
  545.    if(QUERY_FLAG(cauldron,FLAG_DAMNED)) danger +=200;
  546.  
  547.    LOG(llevDebug,"calc_alch_danger() returned danger=%d\n",danger);
  548.    return danger;
  549. }
  550.  
  551.